glcanon: rewrite the G-code preview on OpenGL 3.3 core - #4293
Conversation
Why rewrite?
Performance
Code RefactoringCode is also reviewed and refactored for eaiser maintenance .
Hardware supportThis rewrite targets the OpenGL 3.3 standard, released in 2010 and widely supported. In case of hardware problems, or if drivers don't support the necessary features, the env var Verification
Performance testing resultsBenchmarked against stock master on AXIS end-to-end (4 reps, real GUI,
|
| master | rewrite | ||
|---|---|---|---|
| peak RSS | 1674 MB | 748 MB | 2.24× lower |
| ⤷ minus idle baseline (194 MB both) | 1480 MB | 554 MB | 2.67× lower |
| steady redraw frame | 0.32 s | 0.12 s | 2.7× faster |
open_file_guts (open→drawn) |
7.6 s (6.8–8.5) | 7.1 s (7.1–7.4) | a wash |
⤷ load_preview alone |
3.00 s | 2.96 s | a wash |
Both screenshots confirm the file actually drew — this isn't one branch skipping work.
Phase harness (3 reps, EGL, isolates parse / extents / GPU build)
| phase | master | rewrite |
|---|---|---|
gcode.parse |
1.66 s, +575 MB | 2.76 s, +28 MB |
calc_extents |
0.86 s, +432 MB | 0.00002 s, +0 |
| GPU build | 0.28 s (glNewList), +31 MB |
bake 0.027 s + upload 0.002 s |
| GL payload | not queryable | 24,003,600 B VBO (counted at glBufferData) |
| load total | 2.82 s | 2.80 s |
| peak RSS | 1122 MB | 276 MB |
| first click in preview | +0.75 s, +276 MB (selection lists) | none |
What the numbers say
- Memory is the headline. 2.2× lower peak in the real GUI, and the gap widens on first click: master compiles a second, per-line-named copy of the program into selection display lists (+276 MB); the rewrite's picker reads the buffers already uploaded.
- Load time is unchanged, but for a non-obvious reason. The rewrite's raw parse is ~1.1 s slower — it transforms and fills arrays on the move path. That's fully paid back by
calc_extents/unrotate_previewdropping from 0.86 s to zero, since the rewrite accumulates extents during the fill. If you want load time to actually drop, the parse hot path is where the remaining 1.1 s lives. - Frame time is 2.7× better and the GPU build is 10× cheaper (0.03 s vs 0.28 s).
Two caveats worth stating: llvmpipe puts display lists and VBOs in process RSS, so the RSS deltas are the only cross-branch-comparable "GPU memory" figure — a display list has no queryable size, so master gets no exact byte count. And I noticed that at the same view master's preview saturates to white where the rewrite renders mid-grey; that's a blend difference on densely overlapping geometry which I did not investigate.
|
Good work. Functional touch screen support is rather important to keep working. Breaking a touch interface would probably make quite a few users pulling hairs and may even require baldness treatments when it persists. Some cases that should function: running on RPi4, Rpi5 and running with remote X (also ssh tunneled)? |
I've just verified and touchscreen works exactly as in master branch, so no problems with it. Unfortunately I have no RPi to test on it but I'd not expect any problems, this OpenGL API is not exotic. I'd say previous immediate mode API is less compatible. Regarding remote X11 via SSH, my googling shows it may be a problem, but it is easy to solve by switching to software GL rendering with LIBGL_ALWAYS_SOFTWARE=1. |
|
Tried on my laptop, seems to work great - colors seem a bit darker. Very nice job! |
|
Seems some changes are necessary to make it RPi4B compatible. it seems to be easy. And even more important, it seems we can do much further with this and loading 1M+ points program can be handled well even on weak hardware. I'll work it out and submit here. |
|
Gave this a go with the QtPlasmaC screen. All looks well except two things:
Deleted: Added Deleted: Added Otherwise it seems to work as expected! |
|
Hi!
That is already fixed and I will submit this with updated PR. There was alpha blending set different. Now it pixel-to-pixel matches previous results.
Thank you, I will include this for sure. |
Replaces the fixed-function preview shared by AXIS, the GTK screens and QtVCP with a shader/VBO renderer: a baked trajectory buffer, an offscreen ID-buffer pass for picking, and a glyph atlas for overlay text. The GL matrix stack, display lists, immediate mode and GL_SELECT are gone.
Per-move C->Python callbacks are about two thirds of a 1M-move parse. A canon may now set use_move_batches and supply move_batch, receiving moves and numeric non-move events as fixed-width float64 rows in bulk through a read-only memoryview that reproduces the legacy values bit for bit. Canons that do not opt in keep the call-per-move protocol unchanged.
glnav no longer imports OpenGL at module level, so the camera and its matrix helpers import and test on a host with no GL stack. The fixed-function first-expose lighting moves to an rs274.OpenGLTk.Opengl override that owns the compatibility context, the dead display-list font path goes, and scale() no longer raises NameError on an undefined name.
program_parts copied every plane array so the foam Z offset could be added without mutating the canon array - even when the offset was 0.0, which is every non-foam config. The offset is a rigid Z translation, so it folds into the recorded pass MVP instead, at one site covering the colour, id and override passes. Naming geometry.index also built that lazy property on the load path, for something only the highlight reads; it is resolved on first draw now. Measured at 1M vertices: 16.0 MB copied -> 0, 32.0 MB -> 0 in foam. At the 10M size the buffer layout is sized against, 160 MB of copy and 227 MB of index transient leave the window in which the driver is asked for 240 MB. The VBO itself is unchanged at 24 bytes a move, 40 in foam.
Targeting GL 3.3 core alone ruled out the Raspberry Pi 4 the project ships an official image for: Mesa v3d exposes no desktop core profile at all, and its real API is OpenGL ES 3.1. The renderer now targets the intersection of the two - one implementation, not a second. The GLSL version directive is injected at compile time, the pick pass reads a colour attachment because ES forbids reading depth, glMultiDrawArrays becomes a per-span loop, and narrow buffers quad-expand when the driver refuses the line width asked for. Context creation tries 3.3 core first and falls back to GLES 3.1. Desktop rendering is unchanged: 0 differing pixels across all 17 toggle fixtures.
Compared against a stock-master build rather than against the previous state of this rewrite, the program was compositing alpha whether or not the toggle asked for it - feed pixels landing at (85,85,85) instead of white on 76,763 px of fractal-1M. Blending is gated on the toggle again. With rapids solid the per-vertex dash distance became unreachable, so it and its uniforms go: VERTEX_STRIDE 24 -> 20 bytes, 40 -> 32 in foam. The fill then stopped asking numpy for reductions in the shape it is worst at - the box of 16384 points costs 258 us whole-array against 22 us column by column - for about 4x, producing identical output.
GlNavBase.modelview is now the only camera state and both consumers reload the GL modelview from it every frame, so any glTranslatef() a screen issued outside the camera was discarded on the next frame - the bundled plasmac table view had silently stopped centring. Add translate_modelview(), which post-multiplies exactly as glTranslatef() did, and convert the two bundled call sites. Retires the direct GL access the rewrite left behind: hershey and axis.py no longer import OpenGL, and emcmodule drops the epoxy link.
cfb284d to
f78ed6f
Compare
|
I've updated the code, I think that is now works as it should. I've added the following:
Final result: 5M move file is loading much faster. P.S. During development I found surprising results - regardless of all my optimizations, big ngc file load was still slow and memory eager. I found the reason - AXIS code is not well optimised, and while parsing it eats ~500 bytes per every source code line, it is all with dynamic memory allocation. So if want to directly compare the gremlin/glcanon speed, use qtaxis it seems to be better optimized around this. If you, like me, do not have big .ngc files to try it out, I put some to Dropbox: https://www.dropbox.com/scl/fo/sp3njw8t2hlujo65ql4ma/AMGK-jRiwfUF3G1QJ-4aY8U?rlkey=ewxjww93bbobqd0sb5nxbk289&st=3lribdca&dl=0 |
Some Benchmark resultsTested in AXIS with small patch that disables source code parsing [ by AXIS code ].
Load: 1.23x faster at 1M, 1.46x at 5M. Click-to-line: 8.5x faster at 1M, 33x at 5M — and only the rewrite actually selects anything. Master's first click is dominated by building the selection display lists (0.849 s at 1M, 10.35 s at 5M), then it discards the result: its 100-entry |
A canon that answers unknown attributes with a stub - the catch-all __getattr__ idiom, as tests/interp_initcode's canon uses - handed back a callable for both use_move_batches and move_batch and was opted into the batch protocol without asking, silently dropping its moves into the stub.
be39bbe to
00bedfd
Compare
Replaces the fixed-function preview shared by AXIS, the GTK screens and QtVCP with a shader/VBO renderer: a baked trajectory buffer, an offscreen ID-buffer pass for picking, and a glyph atlas for overlay text. The GL matrix stack, display lists, immediate mode and GL_SELECT are gone.